package main

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"
)

const (
	psTitleFmt = "%-11s%-17s%-49s%-11s\n"                                                              // 打印进程信息的表格头部格式。
	psHr       = "-----------------------------------------------------------------------------------" // 打印进程信息的分割线。
	psRowFmt   = "%-11d%-17s%-49s%-11s\n"                                                              // 打印进程信息的每个进程的格式。

	logsRowFmt = "%-16s | %s\n" // 打印进程输出内容的格式。

	envRowFmt = "%-16s | %s\n" // 打印环境变量的格式。
)

var (
	serviceInfos = make([]*serviceInfo, 0) // 服务列表。

	serviceEvent event // 操作服务的锁，用于各个协程中同步访问服务。

	servicePalette = []string{
		"cyan",
		"magenta",
		"blue",
		"yellow",
		"red",
		"green",
	} // 服务使用的调色板。
)

// 环境变量。
type envItem struct {
	name  string // 环境变量的名字。
	value string // 环境变量的值。
}

// 服务。
type serviceInfo struct {
	name         string           // 服务名。
	workDir      string           // 工作目录。
	cmdLine      string           // 命令行。
	cmd          []string         // 命令行的各个部分（通过命令行解析和替换得到）。
	envItems     []envItem        // 环境变量列表。
	env          []string         // 字符串形式的环境变量列表（通过环境变量列表组合得到）。
	process      *os.Process      // 对应的进程。
	processState *os.ProcessState // 对应的进程的执行结果（进程结束后可用）。
	output       *stdOutput       // 标准输出。
}

// 启动服务的结果。
type startUpResult struct {
	name string // 服务名。
	ok   bool   // 是否成功启动。
	err  error  // 启动服务时产生的错误。
}

// 关闭服务的结果。
type shutDownResult struct {
	name     string // 服务名。
	exitCode int    // 服务进程退出的返回值。
	err      error  // 停止服务时产生的错误。
}

// `serviceInfo` 的方法。
//

func (si *serviceInfo) prepareCmd() error {
	// 生成环境变量列表。
	//
	envMap := make(map[string]string)
	env := []string{}

	addEnv := func(k, v string) error {
		envMap[k] = v
		env = append(env, k+"="+v)
		return nil
	}

	// 添加当前系统环境变量。
	for _, el := range os.Environ() {
		ps := strings.SplitN(el, "=", 2)
		addEnv(ps[0], ps[1])
	}

	// 添加配置文件环境变量。
	for _, aEnvItem := range si.envItems {
		addEnv(aEnvItem.name, aEnvItem.value)
	}

	// 添加预定义环境变量。
	addEnv("FURY_PWD", si.workDir)
	addEnv("FURY_VERSION", AppVersion)

	si.env = env

	// 生成命令行参数。
	//
	cmd := whitespaceSplitter.Split(si.cmdLine, -1)
	userHome, _ := os.UserHomeDir()
	for i := range cmd {
		cmdArg := cmd[i]
		cmdArg = os.Expand(cmdArg, func(s string) string { return envMap[s] })
		if strings.HasPrefix(cmdArg, "~") {
			cmdArg = path.Join(userHome, cmdArg[1:])
		}
		cmd[i] = cmdArg
	}
	si.cmd = cmd

	return nil
}

// 启动指定的服务。
func (si *serviceInfo) start(ch chan startUpResult) {
	si.prepareCmd()

	cmd := exec.Command(si.cmd[0], si.cmd[1:]...)
	buffer := stdOutput{}
	cmd.Dir = si.workDir
	cmd.Env = si.env
	cmd.Stdin = nil
	cmd.Stdout = &buffer
	cmd.Stderr = &buffer
	if err := cmd.Start(); err != nil {
		ch <- startUpResult{si.name, false, err}
		return
	}

	si.process = cmd.Process
	si.processState = nil
	si.output = &buffer

	ch <- startUpResult{si.name, true, nil}

	go func() {
		cmd.Wait()

		si.processState = cmd.ProcessState
	}()
}

// 停止指定的服务。
func (si *serviceInfo) stop(ch chan shutDownResult) {
	if si.process == nil {
		ch <- shutDownResult{si.name, 0, nil}
		return
	}

	if err := killProcess(si.process.Pid); err != nil {
		ch <- shutDownResult{si.name, 0, err}
		return
	}

	go func() {
		// 轮询。每隔200ms检查一次进程是否已退出。
		for {
			if si.processState != nil {
				ch <- shutDownResult{si.name, si.processState.ExitCode(), nil}
				break
			}
			time.Sleep(200 * time.Millisecond)
		}
	}()
}

func (si *serviceInfo) showPs() {
	var status string
	if si.process != nil && si.processState == nil {
		status = "Up"
	} else if si.processState != nil {
		status = fmt.Sprintf("Exited(%d)", si.processState.ExitCode())
	} else {
		status = "-"
	}
	fmt.Printf(psRowFmt, si.process.Pid, si.name, truncateStr(si.cmdLine, 48), status)
}

func (si *serviceInfo) showLogs(color string) {
	for _, line := range si.output.lines {
		fmt.Print(style(color, logsRowFmt, si.name, line))
	}
}

func (si *serviceInfo) showEnv(color string) {
	for _, env := range si.env {
		fmt.Print(style(color, envRowFmt, si.name, env))
	}
}

func findServiceInfo(serviceName string) (*serviceInfo, bool) {
	for i := range serviceInfos {
		si := serviceInfos[i]
		if si.name == serviceName {
			return si, true
		}
	}

	return nil, false
}

func findServiceInfos(serviceNames []string) ([]*serviceInfo, error) {
	if len(serviceNames) == 0 {
		// 所有服务。
		return serviceInfos, nil
	} else {
		// 指定名字的服务。
		result := make([]*serviceInfo, 0)
		for _, serviceName := range serviceNames {
			if si, ok := findServiceInfo(serviceName); ok {
				result = append(result, si)
			} else {
				return nil, fmt.Errorf("no such service: %s", serviceName)
			}
		}
		return result, nil
	}
}

// 创建服务信息集合。
func setUp() {
	serviceEvent.accquire()
	defer serviceEvent.release()

	envItems := []envItem{}
	for envName, envValue := range config.Env {
		envItems = append(envItems, envItem{strings.ToUpper(strings.TrimSpace(envName)), strings.TrimSpace(envValue)})
	}

	for serviceName, serviceConfig := range config.Services {
		serviceName = strings.TrimSpace(serviceName)

		serviceInfos = append(serviceInfos, &serviceInfo{name: serviceName, workDir: serviceConfig.WorkDir, cmdLine: serviceConfig.Cmd, envItems: envItems})
	}
}

// 启动一批服务。
func start(serviceNames []string) {
	if aServiceInfos, err := findServiceInfos(serviceNames); err != nil {
		fmt.Printf("ERROR: %v\n", err)
	} else {
		if !serviceEvent.accquire() {
			return
		}
		defer serviceEvent.release()

		chs := make([]chan startUpResult, len(aServiceInfos))

		for i, aServiceInfo := range aServiceInfos {
			chs[i] = make(chan startUpResult)

			fmt.Printf("Starting service %v ...\n", aServiceInfos[i].name)

			go aServiceInfo.start(chs[i])
		}

		for i := range chs {
			sr := <-chs[i]

			if sr.ok {
				fmt.Printf("Service %s started\n", sr.name)
			} else {
				fmt.Printf("ERROR: Failed to start service %s\n", sr.name)
				if sr.err != nil {
					fmt.Printf("\t%v", sr.err)
				}
			}
		}
	}
}

// 停止一批服务。
func stop(serviceNames []string) {
	if aServiceInfos, err := findServiceInfos(serviceNames); err != nil {
		fmt.Printf("ERROR: %v\n", err)
	} else {
		if !serviceEvent.accquire() {
			return
		}
		defer serviceEvent.release()

		chs := make([]chan shutDownResult, len(aServiceInfos))

		for i, aServiceInfo := range aServiceInfos {
			chs[i] = make(chan shutDownResult)

			fmt.Printf("Stopping service %v ...\n", aServiceInfos[i].name)

			go aServiceInfo.stop(chs[i])
		}

		for i := range chs {
			sr := <-chs[i]

			fmt.Printf("Service %s exited(%d)\n", sr.name, sr.exitCode)
		}
	}
}

// 显示一批服务的进程信息。
func showPs(serviceNames []string) {
	if aServiceInfos, err := findServiceInfos(serviceNames); err != nil {
		fmt.Printf("ERROR: %v\n", err)
	} else {
		if !serviceEvent.accquire() {
			return
		}
		defer serviceEvent.release()

		fmt.Printf(psTitleFmt, "Pid", "Name", "Command", "Status")
		fmt.Println(psHr)
		for _, aServiceInfo := range aServiceInfos {
			aServiceInfo.showPs()
		}
	}
}

// 显示一批服务的标准输出。
func showLogs(serviceNames []string) {
	if aServiceInfos, err := findServiceInfos(serviceNames); err != nil {
		fmt.Printf("ERROR: %v\n", err)
	} else {
		if !serviceEvent.accquire() {
			return
		}
		defer serviceEvent.release()

		for i, aServiceInfo := range aServiceInfos {
			aServiceInfo.showLogs(servicePalette[i%6])
		}
	}
}

// 显示一批服务的环境变量。
func showEnv(serviceNames []string) {
	if aServiceInfos, err := findServiceInfos(serviceNames); err != nil {
		fmt.Printf("ERROR: %v\n", err)
	} else {
		if !serviceEvent.accquire() {
			return
		}
		defer serviceEvent.release()

		for i, aServiceInfo := range aServiceInfos {
			aServiceInfo.showEnv(servicePalette[i%6])
		}
	}
}
